Skip to content

[Refactor] Drop the GGUF draft workarounds now that vLLM covers them - #117

Draft
WhatGhost wants to merge 3 commits into
vllm-project:mainfrom
WhatGhost:dflash-drop-upstream-workarounds
Draft

[Refactor] Drop the GGUF draft workarounds now that vLLM covers them #117
WhatGhost wants to merge 3 commits into
vllm-project:mainfrom
WhatGhost:dflash-drop-upstream-workarounds

Conversation

@WhatGhost

Copy link
Copy Markdown

Stacked on #115, which is stacked on #113 — the first two commits belong to those PRs; this PR is the third commit ae13af7 alone. I'll rebase onto main once both land.

This stays a draft until vllm-project/vllm#53214 lands. It deletes the workarounds that PR makes unnecessary, so it cannot merge before it: without SpeculativeConfig.hf_config_path the key this PR passes through is rejected outright, and without the tolerant speculators probe a .gguf target never reaches this plugin's config parser.

Summary

#113 and #115 each reach into vLLM to get a GGUF draft past a place where a condition that is not an error is treated as one. Upstream now handles both, so the workarounds come out: plugin.py loses 34 lines net and one monkeypatch entirely. Nothing user-visible changes — the launch command is identical before and after, and the acceptance rate is unchanged.

What comes out

The draft names its config source instead of having its weights reference rewritten. SpeculativeConfig now takes hf_config_path, so speculative_config["model"] can stay pointed at the .gguf file. What goes with the rewrite is all the state it needed: #115 popped hf_config_path out of the dict (the field was ours, and SpeculativeConfig rejects keys it does not declare), stashed the original path on engine_args._gguf_draft_weights, and put it back on model_weights afterwards — all so that a second pass over the same EngineArgs, which no longer saw a GGUF path, would not keep the config directory as the weights source and quietly load the unquantized checkpoint sitting there. With model never rewritten, idempotency stops being something to remember and the weights fall back to model the way they do for every other model.

The speculators probe patch is gone. #113 wrapped maybe_override_with_speculators to short-circuit on a GGUF reference, and had to install the wrapper into both arg_utils and transformers_utils.config, since each holds its own reference to it. The probe now treats a reference it cannot read as "not a speculators model", so the wrapper, both module-level imports and both sentinel flags come out. The test that covered it stays, renamed: it pins upstream's behaviour now rather than the plugin's patch, which is also why this PR's suite goes red on an older vLLM.

What does not come out, contrary to what #115 said

#115 listed the {"quant_method": "gguf"} marker as a workaround that "comes out once that check stops raising upstream". That was wrong, and since the promise is on the record it is worth being explicit about. The marker has a second job that is load-bearing: a draft rebuilds its quantization config from hf_config.quantization_config, so that dict is the only channel through which the loader's declaration — which of the draft's modules the GGUF file stores dense — can reach the draft's layers before they are built. _publish_declaration_for_a_draft writes into that dict and returns silently when it is absent, so removing the marker does not fail; it produces a draft that builds quantized layers for dense weights. The function therefore stays, renamed _open_the_drafts_declaration_channel with a docstring that says what it actually does. Its body is unchanged.

That also settles the limitation #115 recorded alongside it: the hf_overrides fallback in get_quant_config never rejects anything here, because planting the dict makes the lookup return at the branch above it. Nothing upstream needs to change there, which is why the upstream PR carries two fixes rather than the three or four first proposed.

Dependencies

Testing

  • 71 tests across test_muse_glimmer_dflash_gguf.py and test_plugin.py, against a vLLM carrying the two upstream fixes. The dflash tests now pin the inverted invariant: model stays on the .gguf file while hf_config_path names the config directory, naming the source twice changes nothing, and an unquantized draft's dict comes back untouched key for key. Two new tests cover the declaration channel, including that an existing declaration is not clobbered.
  • End to end on the real 30B, GGUF target with GGUF draft and 3 speculative tokens: 60.8% acceptance, matching [Models] Support the Muse Glimmer dflash draft model in GGUF #115 with every workaround removed. Sampling the loading process confirms every weight came from a GGUF file and neither the target's nor the draft's unquantized safetensors was touched.
  • The same pair through vllm serve, which is the path the removed hf_config_path handling has to survive: the server starts, the draft resolves its own architecture from hf_config_path, and speculative decoding is live on the served request (42 of 66 draft tokens accepted).

WhatGhost and others added 3 commits August 20, 2026 01:54
Converting this architecture to GGUF is not a pure requantization.  Four
things change on the way in, and each one loads without complaint and
produces fluent but wrong output if it is not undone: the Q/K rows are
re-laid out from the half-split NEOX order into llama.cpp's interleaved
order, the per-layer norms have the architecture's `1 +` folded into the
stored weight, the Q/K norms are synthesized from the config's scale factor
rather than stored as learned parameters, and the vision patch embedding is
reduced to the sum of its per-time-step blocks.

The adapter reverses the first three exactly.  The Q/K permutation is
applied to the packed bytes directly, since GGUF splits super-blocks along
the input dimension and so leaves each output row self-contained.

The fourth is exact for still images, which depend on the sum alone because
the encoder expands one patch to every time step.  Video depends on the
blocks individually and cannot be recovered, so it is declared unsupported
and rejected during input validation rather than served from a
reconstruction that is off by about 7% in the channel carrying motion.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: whatghost <yuyang.gao@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: whatghost <yuyang.gao@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: whatghost <yuyang.gao@amd.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant